/************************************************************
 * Copyright (c) 2010 Georg Fritzsche
 *
 * This project is licensed under the "New BSD License": 
 * http://www.opensource.org/licenses/bsd-license.php
 ***********************************************************/

#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
#include <iostream>
#include <sstream>
#include <map>

#include <boost/make_shared.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/algorithm/string/trim.hpp>

#include <fmod.hpp>
#include <fmod_errors.h>

#include "fmod_util.h"
#include "player.h"
#include "midi_listener.h"
#include "audio_system.h"
#include "util.h"
#include "config.h"


namespace
{
    

    void fmod_system_deleter(FMOD::System* s)
    {
        s->close();
        s->release();
    }

    boost::shared_ptr<FMOD::System> init_fmod(int deviceNumber = -1)
    {
	    FMOD_RESULT fmRes;

	    /* init fmod */

        FMOD::System* system_ptr;
	    fmRes = FMOD::System_Create(&system_ptr);
	    FmodErrThrow(fmRes);

        boost::shared_ptr<FMOD::System> system(system_ptr, &fmod_system_deleter);

        /* get version */

        unsigned int fmVersion;
	    fmRes = system->getVersion(&fmVersion);
	    FmodErrThrow(fmRes);

	    if(fmVersion < FMOD_VERSION) {
		    std::ostringstream s;
		    s << "old version of FMOD - " << std::hex << (int)fmVersion << "."
		      << "This program requires " << std::hex << (int)FMOD_VERSION << "." << std::endl;
		    throw std::logic_error(s.str());
	    }
        
        if (deviceNumber >= 0) {
            fmRes = system->setDriver(deviceNumber);
            FmodErrThrow(fmRes);
        }

	    fmRes = system->init(32, FMOD_INIT_NORMAL, 0);
	    FmodErrThrow(fmRes);

        return system;
    }
    
    void list_audio_devices(const boost::shared_ptr<FMOD::System>& system)
    {
        FMOD_RESULT fmRes;
        int driverCount;
        
        fmRes = system->getNumDrivers(&driverCount);
        FmodErrThrow(fmRes);
        
        char driverName[100];
        const int nameLen = sizeof(driverName) / sizeof(driverName[0]);
        
        trace("Audio devices: ");
        
        for (int i=0; i<driverCount; ++i)
        {
            fmRes = system->getDriverInfo(i, driverName, nameLen, 0);
            FmodErrThrow(fmRes);            
            trace(" * device #" << i << ": " << driverName);
        }
    }

    void main_loop(const boost::shared_ptr<Player>& player)
    {
        bool run = true;
        std::string input;

        while (run && std::getline(std::cin, input))
	    {
            boost::algorithm::trim(input);

            if (input == "q") {
                run = false;
            } else if (input == "n") {
                player->next();
            } else if (input == "r") {
                player->reset();
            } else {
                trace("Use 'n' for next, 'r' for reset, 'q' to quit.");
            }
	    }
    }
}

int main(int argc, char** argv)
{
    try
    {
        Config config(argc, argv);
        
        if (config.exitNow()) {
            return 0;
        }
        
        if (config.listDevices()) {
            MidiListener::listPorts();
            AudioSystem audio;
            audio.listDevices();            
            return 0;
        }

        boost::shared_ptr<AudioSystem>    system = boost::make_shared<AudioSystem >(config);
        boost::shared_ptr<Player>         player = boost::make_shared<Player      >(system, config);
        boost::shared_ptr<MidiListener> listener = boost::make_shared<MidiListener>(player, config);

        main_loop(player);
    } 
    catch(const config_error& e) 
    {
        tracee("Config error: " << e.what());
    }
    catch(const fmod_error& e) 
    {
        tracee("FMOD error: " << e.what());
    }
    catch(const midi_error& e) 
    {
        tracei("MIDI error: " << e.what());
    }
    catch(const std::exception& e) 
    {
        tracee("error: " << e.what());
    }    

    trace("\nQuitting ...");
}

